package com.javacoo.junit.generator.internal.plugin;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.javacoo.junit.generator.api.plugin.TemplatePlugin;
import com.javacoo.junit.generator.internal.AssertClause;
import com.javacoo.junit.generator.internal.MethodHelper;
import com.javacoo.junit.generator.internal.VariableNames;
import com.javacoo.junit.generator.internal.util.StrKit;
import com.javacoo.junit.generator.internal.util.StringUtility;
import com.javacoo.junit.generator.model.MethodHandleContext;
import com.javacoo.junit.generator.model.MethodMetadata;

import static com.javacoo.junit.generator.internal.util.Formats.BLANK_1;
import static com.javacoo.junit.generator.internal.util.Formats.BLANK_4;
import static com.javacoo.junit.generator.internal.util.Formats.BLANK_8;
import static com.javacoo.junit.generator.internal.util.Formats.RT_1;

/**
 * 模板插件接口抽象实现类
 * <li>定义了插件所需公共方法及流程</li>
 *
 * @author: duanyong@jccfc.com
 * @since: 2021/1/5 14:27
 */
public abstract class AbstractTemplatePlugin implements TemplatePlugin {
    /**默认模板路径*/
    protected static final String BASE_TEMPLATE_PACKAGE = "/templates/";
    /**日期格式*/
    private final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    /**返回变量名*/
    private final String RESULT_VAL_NAME = "actualResult";

    /**
     * 是否需要定义测试类变量
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 17:18
     * @return: boolean
     */
    protected boolean needDefineVal(){
        return true;
    }
    /**
     * 构建模板公共数据Map对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 13:54
     * @param sourceClass: 目标class对象
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     */
    protected Map<String, Object> buildCommonDataMap(Class sourceClass) {
        // 定义模板数据
        Map<String, Object> data = new HashMap<>(6);
        //组装基础数据到模板数据Map对象
        populateBaseData(sourceClass, data);
        //组装方法数据到模板数据Map对象
        populateMethodMetaData(sourceClass, data);

        return data;
    }
    /**
     * 组装基础数据
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 14:05
     * @param sourceClass: 目标对象
     * @param data:模板数据
     * @return: void
     */
    private void populateBaseData(Class sourceClass, Map<String, Object> data) {
        Package sourcePackage = sourceClass.getPackage();
        data.put(TemplatePlugin.TEPMLATE_KEY_BASE_PACKAGE, sourcePackage.getName());
        data.put(TemplatePlugin.TEPMLATE_KEY_DATE, LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_FORMAT)));
        data.put(TemplatePlugin.TEPMLATE_KEY_CLASS_NAME, sourceClass.getSimpleName());
    }

    /**
     * 组装方法元数据
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 14:02
     * @param sourceClass: 目标对象
     * @param data: 模板数据Map
     * @return: void
     */
    private void populateMethodMetaData(Class sourceClass, Map<String, Object> data) {
        Method[] methods = sourceClass.getDeclaredMethods();
        //定义方法元数据集合
        List<MethodMetadata> methodList = new ArrayList<>();
        //定义依赖名称集合
        List<String> importClasses = new ArrayList<>();

        for (Method method : methods) {
            handleMethod(sourceClass, methodList, importClasses, method);
        }

        //设置依赖对象集合
        data.put(TemplatePlugin.TEPMLATE_KEY_IMPORT_CLASSES,importClasses);
        //设置方法元数据对象集合
        data.put(TemplatePlugin.TEPMLATE_KEY_METHODS,methodList);
    }
    /**
     * 处理方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 14:45
     * @param sourceClass:目标类对象
     * @param methodList:元数据集合
     * @param importClasses:依赖对象名集合
     * @param method:方法对象
     * @return: void
     */
    private void handleMethod(Class sourceClass, List<MethodMetadata> methodList, List<String> importClasses,
        Method method) {
        //构建方法处理上下文
        MethodHandleContext methodHandleContext = buildMethodHandleContext(sourceClass,method);
        //执行方法处理
        doHandleMethod(methodHandleContext);

        //构建方法元数据
        MethodMetadata methodData = MethodMetadata.builder()
            .methodBody(methodHandleContext.getMethodBody().toString())
            .methodName(methodHandleContext.getMethod().getName())
            .build();
        methodList.add(methodData);
        //添加需要导入的类
        if(!methodHandleContext.getDependenceClasses().isEmpty()){
            importClasses.addAll(methodHandleContext.getDependenceClasses());
        }
    }
    /**
     * 构建方法处理上下文对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/27 10:01
     * @param sourceClass:方法所属class对象
     * @param method:方法对象
     * @return: com.javacoo.junit.generator.model.MethodHandleContext 法处理上下文对象
     */
    private MethodHandleContext buildMethodHandleContext(Class sourceClass,Method method){
        //依赖的对象集合
        List<String> dependenceClasses = new ArrayList<>(5);
        //测试参数
        StringBuilder testParams = new StringBuilder();
        //变量名对象
        VariableNames variableNames = new VariableNames();
        //类名
        String className = MethodHelper.getClassName(sourceClass,dependenceClasses);
        //返回类型
        Class returnType = method.getReturnType();
        //返回类型字符串
        String returnTypeName = MethodHelper.getClassName(returnType,dependenceClasses);
        //是否有返回类型
        boolean hasReturn = !"void".equals(returnTypeName);
        //是否有参数
        boolean hasParams = method.getParameterTypes().length > 0;
        //是否是静态方法
        boolean isStatic = Modifier.isStatic(method.getModifiers());
        //是否是私有方法
        boolean isPrivate = Modifier.isPrivate(method.getModifiers());
        //是否是抽象类
        boolean abstractClass = Modifier.isAbstract(sourceClass.getModifiers());
        //是否抛出异常
        String exception = MethodHelper.needTryCatch(method);
        if (exception == null && isPrivate) {
            exception = "Exception";
        }
        //参数类型
        Type[] argTypes = MethodHelper.isGenericMode(method) ? method.getGenericParameterTypes() : method.getParameterTypes();
        //预期的变量名
        String[] expectedCallVarNames = MethodHelper.getExpectedVarNames(method, sourceClass);
        //调用参数
        String invokeParams = MethodHelper.getArgs(argTypes, expectedCallVarNames,testParams,dependenceClasses,variableNames);
        //方法返回前缀
        String prefix = !hasReturn ? BLANK_8 : BLANK_8 + returnTypeName + " " + RESULT_VAL_NAME + " = ";

        return MethodHandleContext.builder()
            .sourceClass(sourceClass)
            .abstractClass(abstractClass)
            .method(method)
            .methodName(method.getName())
            .methodBody(new StringBuilder())
            .variableNames(variableNames)
            .dependenceClasses(dependenceClasses)
            .className(className)
            .hasReturn(hasReturn)
            .returnType(returnType)
            .returnTypeName(returnTypeName)
            .hasParams(hasParams)
            .isStatic(isStatic)
            .isPrivate(isPrivate)
            .exception(exception)
            .invokeParams(invokeParams)
            .testParams(testParams)
            .prefix(prefix)
            .build();
    }
    /**
     * 执行方法体处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:42
     * @param methodHandleContext: 方法处理上下文对象
     * @return: void
     */
    private void doHandleMethod(MethodHandleContext methodHandleContext){
        //生成方法体前处理
        handleMethodBodyBefore(methodHandleContext);
        //处理方法体备注
        handleRemark(methodHandleContext);
        //实例化处理
        handleInstance(methodHandleContext);
        //准备测试参数
        handlePrepareParams(methodHandleContext);
        //处理私有方法前处理
        handlePrivateBefore(methodHandleContext);
        //异常前处理
        handleExceptionBefore(methodHandleContext);
        //处理私有方法
        handlePrivate(methodHandleContext);
        //处理普通方法
        handleNormal(methodHandleContext);
        //处理返回
        handleReturn(methodHandleContext);
        //生成方法体后处理
        handleMethodBodyAfter(methodHandleContext);
        //异常后处理
        handleExceptionAfter(methodHandleContext);
    }
    /**
     * 处理方法体备注
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:12
     * @param methodHandleContext: 方法处理上下文对象
     * @return: void
     */
    private void handleRemark(MethodHandleContext methodHandleContext) {
        methodHandleContext.getMethodBody().append(BLANK_4 + "//TODO: 检查生成的测试代码"
                + (methodHandleContext.isHasParams() ? ", 修改给定的方法调用参数" : "")
                + (!methodHandleContext.isHasReturn() ? "" : "和" + (methodHandleContext.isHasParams() ? "" : "修改给定的 ") + "断言子句") + RT_1);
        methodHandleContext.getMethodBody().append(BLANK_8 + "//" + (methodHandleContext.isHasParams() ? "准备参数并 " : "") + "调用测试方法" + RT_1);
    }
    /**
     * 私有方法前置处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:38
     * @param methodHandleContext:方法处理上下文
     * @return: void
     */
    private void handlePrivateBefore(MethodHandleContext methodHandleContext) {
        if (!methodHandleContext.isPrivate() || !StringUtility.stringHasValue(methodHandleContext.getInvokeParams())) {
            return;
        }
        methodHandleContext.getMethodBody().append(BLANK_8 + "Object[] testMethodArgs = {" + methodHandleContext.getInvokeParams() + "};" + RT_1);
    }
    /**
     * 方法返回处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:37
     * @param methodHandleContext: 方法处理上下文
     * @return: void
     */
    private void handleReturn(MethodHandleContext methodHandleContext) {
        if (!methodHandleContext.isHasReturn()) {
            return;
        }
        methodHandleContext.getMethodBody().append(BLANK_8 + (methodHandleContext.getException() != null ? BLANK_4 : "") + AssertClause.get(methodHandleContext.getReturnType(),methodHandleContext.getMethodName(), RESULT_VAL_NAME) + RT_1);
    }
    /**
     * 异常前处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:36
     * @param methodHandleContext: 方法处理上下文
     * @return: void
     */
    private void handleExceptionAfter(MethodHandleContext methodHandleContext) {
        if (methodHandleContext.getException() == null) {
            return;
        }
        String exceptionVarName = methodHandleContext.getVariableNames().get("ex", true);
        methodHandleContext.getMethodBody().append(BLANK_8 + "} catch (" + methodHandleContext.getException() + " " + exceptionVarName + ") {" + RT_1 + BLANK_8 + BLANK_4 + exceptionVarName + ".printStackTrace();" + RT_1 + BLANK_4 + BLANK_8 + "fail(\"Unexpected exception: \"" + " + " + exceptionVarName + ");" + RT_1 + BLANK_8 + "}" + RT_1);
    }
    /**
     * 异常前处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:33
     * @param methodHandleContext: 方法处理上下文
     * @return: void
     */
    private void handleExceptionBefore(MethodHandleContext methodHandleContext) {
        if (methodHandleContext.getException() == null) {
            return;
        }
        methodHandleContext.getMethodBody().append(BLANK_8 + "try {" + RT_1);
        methodHandleContext.setPrefix(BLANK_4 + methodHandleContext.getPrefix());
    }
    /**
     * 处理普通方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:33
     * @param methodHandleContext: 方法处理上下文
     * @return: void
     */
    private void handleNormal(MethodHandleContext methodHandleContext) {
        if (methodHandleContext.isPrivate()) {
            return;
        }
        if (methodHandleContext.isStatic()) {
            methodHandleContext.getMethodBody().append(methodHandleContext.getPrefix() + methodHandleContext.getClassName() + "." + methodHandleContext.getMethodName() + "(" + methodHandleContext.getInvokeParams() + ");" + RT_1);
        } else {
            methodHandleContext.getMethodBody().append(methodHandleContext.getPrefix() + methodHandleContext.getInstanceName() + "." + methodHandleContext.getMethodName() + "(" + methodHandleContext.getInvokeParams() + ");" + RT_1);
        }
    }

    /**
     * 处理私有方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 9:56
     * @param methodHandleContext:方法处理上下文
     * @return: void
     */
    private void handlePrivate(MethodHandleContext methodHandleContext) {
        if (!methodHandleContext.isPrivate()) {
            return;
        }
        if (!methodHandleContext.getDependenceClasses().contains("java.lang.reflect.Method")) {
            methodHandleContext.getDependenceClasses().add("java.lang.reflect.Method");
        }
        String methodParamClasses = "";
        for (int j = 0; j < methodHandleContext.getMethod().getParameterTypes().length; j++) {
            String typeName = methodHandleContext.getMethod().getParameterTypes()[j].getTypeName();
            if (typeName.contains("$")) {
                typeName = typeName.replace("$", ".");
            }
            methodParamClasses += ", " + typeName + ".class";
        }

        String methodDeclaringName = "method" + StrKit.CapsFirst(methodHandleContext.getMethodName());
        methodHandleContext.getMethodBody().append(BLANK_8 + BLANK_4 + "Method " + methodDeclaringName + " = " + methodHandleContext.getClassName() + ".class.getDeclaredMethod(" + "\"" + methodHandleContext.getMethodName() + "\"" + methodParamClasses + ");" + RT_1);
        methodHandleContext.getMethodBody().append(BLANK_8 + BLANK_4 + methodDeclaringName + ".setAccessible(true);" + RT_1);

        String strongConvert = !methodHandleContext.isHasReturn() ? "" : "(" + methodHandleContext.getReturnTypeName() + ") ";
        String invokeObj = methodHandleContext.getInstanceName();
        if (methodHandleContext.isStatic()) {
            invokeObj = "null";
        }
        methodHandleContext.getMethodBody().append(methodHandleContext.getPrefix() + strongConvert + methodDeclaringName + ".invoke(" + invokeObj + ("".equals(methodHandleContext.getInvokeParams()) ? "" : ", testMethodArgs") + ");" + RT_1);
    }

    /**
     * 实例化处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/27 16:39
     * @param methodHandleContext:方法处理上下文
     * @return: void
     */
    private void handleInstance(MethodHandleContext methodHandleContext) {
        String instanceName = null;
        if (!methodHandleContext.isStatic()) {
            instanceName = methodHandleContext.getVariableNames().get(methodHandleContext.getSourceClass());
            if (!methodHandleContext.isAbstractClass()) {
                //是否需要定义类变量
                if(needDefineVal()){
                    instanceName = instanceClassHandle(methodHandleContext, methodHandleContext.getSourceClass(),methodHandleContext.getClassName());
                }else{
                    MethodHelper.genInstanceName(methodHandleContext.getMethodBody(), methodHandleContext.getClassName(), instanceName,needDefineVal());
                }
            } else {
                //是否需要定义类变量
                if(needDefineVal()){
                    //测试类实例名
                    Class implClass = MethodHelper.getImplClass(methodHandleContext.getSourceClass());
                    if(implClass != null){
                        String className = implClass.getSimpleName();
                        instanceName = instanceClassHandle(methodHandleContext,implClass,className);
                    }else{
                        MethodHelper.genInstanceName(methodHandleContext.getMethodBody(), methodHandleContext.getClassName(), instanceName,needDefineVal());
                    }
                }else{
                    MethodHelper.genInstanceName(methodHandleContext.getMethodBody(), methodHandleContext.getClassName(), instanceName,needDefineVal());
                }
            }
        }
        //设置实例名称
        methodHandleContext.setInstanceName(instanceName);
    }
    /**
     * 准备测试参数
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/28 10:15
     * @param methodHandleContext: 方法处理上下文对象
     * @return: void
     */
    protected void handlePrepareParams(MethodHandleContext methodHandleContext) {
        //添加测试数据
        methodHandleContext.getMethodBody().append(methodHandleContext.getTestParams());
    }
    /**
     * 生成方法体前处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/26 16:53
     * @param methodHandleContext: 方法处理上下文对象
     * @return: void
     */
    protected void handleMethodBodyBefore(MethodHandleContext methodHandleContext) {

    }
    /**
     * 生成方法体后处理
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/26 21:29
     * @param methodHandleContext: 方法处理上下文对象
     */
    protected void handleMethodBodyAfter(MethodHandleContext methodHandleContext) {

    }

    /**
     * 实例化类处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/12 14:41
     * @param methodHandleContext: 方法处理上下文
     * @return: String
     */
    private String instanceClassHandle(MethodHandleContext methodHandleContext,Class sourceClass,String className) {
        String instanceName;
        Constructor ctor = MethodHelper.chooseConstructor(sourceClass.getConstructors(), true);
        if (ctor != null) {
            String[] expectedVarNames = MethodHelper.getExpectedVarNames(ctor, sourceClass);
            Class[] paramTypes = ctor.getParameterTypes();
            String args = MethodHelper.getSimpleArgs(paramTypes, expectedVarNames,methodHandleContext.getMethodBody(),methodHandleContext.getDependenceClasses(),methodHandleContext.getVariableNames(),needDefineVal());

            instanceName = methodHandleContext.getVariableNames().get(sourceClass);
            String ex = MethodHelper.needTryCatch(ctor);
            if (ex != null) {
                MethodHelper.genInstanceName(methodHandleContext.getMethodBody(), methodHandleContext.getClassName(), instanceName,needDefineVal());
                methodHandleContext.getMethodBody().append(BLANK_8 + "try {" + RT_1
                    + BLANK_8 + BLANK_4 + instanceName + " = new " + className + "(" + args + ");" + RT_1);
                String exceptionVarName = methodHandleContext.getVariableNames().get("ex", false);
                methodHandleContext.getMethodBody().append(BLANK_8 + "} catch (" + ex + " " + exceptionVarName + ") {" + RT_1
                    + BLANK_8 + BLANK_4 + exceptionVarName + ".printStackTrace();" + RT_1
                    + BLANK_8 + BLANK_4 + "fail(\"Unexpected exception: \"" + " + " + exceptionVarName + ");" + RT_1 + BLANK_8 + "}" + RT_1);
            } else {
                methodHandleContext.getMethodBody().append(BLANK_8 + methodHandleContext.getClassName() + BLANK_1 + instanceName + " = new " + className + "(" + args + ");" + RT_1);
            }
        } else {
            instanceName = methodHandleContext.getVariableNames().get(sourceClass);
            MethodHelper.genInstanceName(methodHandleContext.getMethodBody(), methodHandleContext.getClassName(), instanceName,needDefineVal());
        }
        return instanceName;
    }





}
